package com.stark.commons.spring.core.aspect;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.integration.support.locks.LockRegistry;

import com.stark.commons.lang.util.Utils;
import com.stark.commons.spring.core.annotation.DistributedLock;
import com.stark.commons.spring.core.exception.DistributedLockException;
import com.stark.commons.spring.core.support.aop.MethodExpressionEvaluator;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 分布式锁切面切面。
 * <p>作用于所有 {@link com.stark.commons.spring.core.annotation.DistributedLock @cDistributedLock} 注解的方法。
 * @author Ben
 * @since 1.0.0
 * @version 1.0.0
 */
@Aspect
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class DistributedLockAspect implements Ordered {
	
	private LockRegistry lockRegistry;
	
	private long timeout;
	
	@Override
	public int getOrder() {
		return Ordered.HIGHEST_PRECEDENCE;
	}
	
	@Around("@annotation(com.stark.commons.spring.core.annotation.DistributedLock)")
	public Object lock(ProceedingJoinPoint joinPoint) throws Throwable {
		Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
		DistributedLock lockConfig = method.getAnnotation(DistributedLock.class);
		MethodExpressionEvaluator evaluator = new MethodExpressionEvaluator(joinPoint);
		String lockKey = String.valueOf(evaluator.getValue(lockConfig.key()));
		String target = evaluator.getTargetClass().getName() + "#" + evaluator.getTargetMethod().getName();
		long _timeout = lockConfig.timeout() > 0
				? lockConfig.timeout()
				: timeout;
		boolean log = lockConfig.log();
		
		String id = Utils.createUUID();
		if (log) {
			logInfo("-->" + timestamp() + " --- [DistributedLockAspect] 尝试占锁: id=" + id
					+ "\n   target=" + target
					+ "\n   lockKey=" + lockKey
					+ "\n   timeout=" + _timeout);
		}
		Lock lock = lockRegistry.obtain(lockKey);
		boolean isLocked = false;
		try {
			isLocked = _timeout > 0
					? lock.tryLock(_timeout, TimeUnit.MILLISECONDS)
					: lock.tryLock();
		} catch (Exception e) {
			if (log) {
				logInfo("-->" + timestamp() + " --- [DistributedLockAspect] 占锁失败: id=" + id
						+ "\n   target=" + target
						+ "\n   lockKey=" + lockKey
						+ "\n   timeout=" + _timeout
						+ "\n   errmsg=" + e.getMessage());
			}
			throw new DistributedLockException("占锁失败: target=" + target + ",lockKey=" + lockKey + ",timeout=" + _timeout, e);
		}
		if (!isLocked) {
			if (log) {
				logInfo("-->" + timestamp() + " --- [DistributedLockAspect] 占锁失败: id=" + id
						+ "\n   target=" + target
						+ "\n   lockKey=" + lockKey
						+ "\n   timeout=" + _timeout);
			}
			throw new DistributedLockException("占锁失败: target=" + target + ",lockKey=" + lockKey + ",timeout=" + _timeout);
		}
	
		if (log) {
			logInfo("-->" + timestamp() + " --- [DistributedLockAspect] 占锁成功: id=" + id
					+ "\n   target=" + target
					+ "\n   lockKey=" + lockKey);
		}
		try {
			Object result = joinPoint.proceed();
			return result;
		} finally {
			try {
				lock.unlock();
			} catch (Exception e) {
				// Lock was released in the store due to expiration, do nothing.
			}
			if (log) {
				logInfo("<--" + timestamp() + " --- [DistributedLockAspect] 解锁成功: id=" + id
						+ "\n   target=" + target
						+ "\n   lockKey=" + lockKey
						+ "\n   timeout=" + _timeout);
			}
		}
	}
	
	private String timestamp() {
		return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
	}
	
	private void logInfo(String msg) {
		if (log.isInfoEnabled()) {
			log.info("\n" + msg);
		} else {
			System.out.println(msg);
		}
	}
	
}
